how to join and map using prisma client

aliases
No value
tags
No value
description
No value
status
μž‘μ„±μ™„λ£Œ
project
false
area
true
resource
false
title
how to join and map using prisma client
created
2025-02-20T22:02:08
updated
2025-04-12T16:13:23

My case

AcademyCoach와 User μ‚¬μ΄μ˜ λ¦΄λ ˆμ΄μ…˜μ„ μ‚¬μš©ν•˜λ˜, userModelμ΄λΌλŠ” 기본으둜 μ§€μ •λœ ν•„λ“œκ°€ μ•„λ‹Œ userInfoλΌλŠ” μž„μ˜μ˜ ν•„λ“œμ— User μ—”ν‹°ν‹°λ₯Ό λ§€ν•‘ν•˜κΈ° μœ„ν•΄ μ‚¬μš©ν•œλ‹€.

  async listAcademyCoach(
    academyId: string,
  ): Promise<AcademyCoachDtoResponse[]> {
    return await this.prisma.academyCoach
      .findMany({
        include: {
          userModel: true, // JOIN and select related user entity into userModel
        },
        where: {
          academy: academyId,
        },
      })
      .then((dtos) =>
        dtos.map((dto): AcademyCoachDtoResponse => {
          const { userModel, ...rest } = dto;
          return {
            ...rest,
            userInfo: UserDto.from(userModel), // ← λ°”λ‘œ μ—¬κΈ°μ—μ„œ 맀핑이 이루어진닀.
          };
        }),
      );
  }

μ•„λž˜λŠ” Prisma Schema

model User {
  id           String       @id @default(auto()) @map("_id") @db.ObjectId
  name         String

  createdTime DateTime  @default(now()) @db.Date
  updatedTime DateTime  @updatedAt @db.Date
  deletedTime DateTime? @db.Date

  AcademyCoach            AcademyCoach[]
  tickets                 Ticket[]

  @@map("usermodels")
}
model AcademyCoach {
  id          String @id @default(auto()) @map("_id") @db.ObjectId
  academy     String @db.ObjectId
  user        String @db.ObjectId

  createdTime DateTime  @default(now()) @db.Date
  updatedTime DateTime  @updatedAt @db.Date
  deletedTime DateTime? @db.Date
  
  userModel     User   @relation(fields: [user], references: [id])
  academyModel  Academy @relation(fields: [academy], references: [id])

  @@map("academycoachmodels")
}

GPT μ„€μ „

is there any way of mapping joined property in prisma? In TypeORM there is a method such as leftJoinAndMapOne can achieve my case.

Yes, Prisma does not have a direct equivalent to TypeORM's leftJoinAndMapOne, but you can achieve similar functionality using relations and computed fields.

Solution 1: Using include or select

If the relation is defined in your schema, you can use include or select to fetch the related entity.

Example:

model User {
  id    Int    @id @default(autoincrement())
  name  String
  profile Profile? @relation(fields: [profileId], references: [id])
  profileId Int?
}

model Profile {
  id    Int    @id @default(autoincrement())
  bio   String
  user  User?
}

To fetch User with the Profile included:

const users = await prisma.user.findMany({
  include: {
    profile: true,
  },
});

Solution 2: Using computed properties via a Service Layer

Since Prisma doesn't allow mapping a joined property directly like TypeORM's leftJoinAndMapOne, you can manually add the joined property in your service layer.

const usersWithMappedProfile = await prisma.user.findMany({
  include: {
    profile: true,
  },
});

const result = usersWithMappedProfile.map(user => ({
  ...user,
  profileBio: user.profile?.bio || null,
}));

This effectively adds a profileBio field like leftJoinAndMapOne does in TypeORM.


Solution 3: Raw SQL with prisma.$queryRaw

If you need a more complex join:

const usersWithProfile = await prisma.$queryRaw`
  SELECT u.id, u.name, p.bio AS profileBio
  FROM User u
  LEFT JOIN Profile p ON u.profileId = p.id;
`;

But this approach does not return a typed model like Prisma usually does.


Conclusion

While Prisma does not have an exact equivalent of leftJoinAndMapOne, the combination of:

  1. include / select for relations
  2. Transforming the result in your service layer
  3. Using raw SQL queries when necessary

achieves the same effect. The most idiomatic Prisma approach is to use include and process the mapping at the service layer.

2025-04-12 ν˜„μž¬λŠ” Dto νŒ©ν† λ¦¬ λ©”μ„œλ“œμ— λ§€ν•‘ λ‘œμ§μ„ 담아놓고 있음.

export class TicketOrderDto extends DtoTransform {
  static from(
    input: TicketOrder & {
      userModel?: User;
      ticketModel?: Ticket & {
        ticketPlanModel?: TicketPlan;
      };
    },
  ): TicketOrderDto {
    const dto = this.transform<typeof input, TicketOrderDto>(input);
    // μ—°κ΄€ ν•„λ“œ λ§€ν•‘
    dto.ticket = input.ticketModel
      ? TicketWithReservationTimeDto.from(input.ticketModel)
      : undefined;
    dto.userDto = input.userModel ? UserDto.from(input.userModel) : undefined;
    dto.ticketPlanDto = input.ticketModel?.ticketPlanModel
      ? TicketPlanDto.from(input.ticketModel.ticketPlanModel)
      : undefined;
    return dto;
  }

  ...
  @NestedProperty({ type: TicketPlanDto, optional: true })
  ticketPlanDto?: TicketPlanDto;

  @NestedProperty({ type: TicketWithReservationTimeDto, optional: true })
  ticket?: TicketWithReservationTimeDto;

  @NestedProperty({ type: UserDto, optional: true })
  userDto?: UserDto;
}